﻿#region Disclaimer/Info
///////////////////////////////////////////////////////////////////////////////////////////////////
// Subtext WebLog
// 
// Subtext is an open source weblog system that is a fork of the .TEXT
// weblog system.
//
// For updated news and information please visit http://subtextproject.com/
// Subtext is hosted at SourceForge at http://sourceforge.net/projects/subtext
// The development mailing list is at subtext-devs@lists.sourceforge.net 
//
// This project is licensed under the BSD license.  See the License.txt file for more information.
///////////////////////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI.WebControls;
using System.Xml;
using Sgml;

using System.Collections.Generic;
using NLBS.RegularExpressions;

namespace NLBS.Text
{
    /// <summary>
    /// Static class used for parseing, formatting, and validating HTML.
    /// </summary>
    public static partial class HtmlHelper
    {
       

        /// <summary>
        /// Strips HTML tags from the specified text.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <returns></returns>
        public static string RemoveHtml(string text)
        {
            if (!String.IsNullOrEmpty(text))
            {
                HtmlTagRegex regex = new HtmlTagRegex();
                return regex.Replace(text, string.Empty);
            }
            return text;
        }

        /// <summary>
        /// Strips HTML comments from the specified text
        /// </summary>
        /// <param name="text">The text</param>
        /// <returns>The text without comments</returns>
        public static string RemoveHtmlComments(string text)
        {
            if (!String.IsNullOrEmpty(text))
            {
                Regex commentStripper = new Regex("(?:&lt;!--)(?:(\\w|[ ]|\\W)*)(?:--&gt;)|(?:<!--)(?:(\\w|[ ]|\\W)*)(?:-->)");
                return commentStripper.Replace(text, string.Empty);
            }
            return text;
        }


        /// <summary>
        /// Strips HTML tags and comments from the specified text
        /// </summary>
        /// <param name="text">The text</param>
        /// <returns>The text without tags and comments</returns>
        public static string RemoveHtmlAndComments(string text)
        {
            if (!String.IsNullOrEmpty(text))
            {
                text = HtmlHelper.RemoveHtmlComments(text);
                text = HtmlHelper.RemoveHtml(text);
            }
            return text;
        }

        /// <summary>
        /// Converts the entry body into XHTML compliant text. 
        /// Returns false if it encounters a problem in doing so.
        /// </summary>
        /// <param name="entry">Entry.</param>
        /// <returns></returns>
        //public static bool ConvertHtmlToXHtml(Entry entry)
        //{
        //    SgmlReader reader = new SgmlReader();
        //    reader.SetBaseUri(Config.CurrentBlog.RootUrl.ToString());
        //    reader.DocType = "html";
        //    reader.WhitespaceHandling = WhitespaceHandling.All;
        //    reader.InputStream = new StringReader("<html>" + entry.Body + "</html>");
        //    reader.CaseFolding = CaseFolding.ToLower;
        //    StringWriter writer = new StringWriter();
        //    XmlTextWriter xmlWriter = null;
        //    try
        //    {
        //        xmlWriter = new XmlTextWriter(writer);

        //        while (reader.Read())
        //        {
        //            xmlWriter.WriteNode(reader, true);
        //        }
        //    }
        //    finally
        //    {
        //        if (xmlWriter != null)
        //        {
        //            xmlWriter.Close();
        //        }
        //    }

        //    string xml = writer.ToString();
        //    entry.Body = xml.Substring("<html>".Length, xml.Length - "<html></html>".Length);
        //    return true;
        //}

        /// <summary>
        /// Tests the specified string looking for illegal characters 
        /// or html tags.
        /// </summary>
        /// <param name="s">S.</param>
        /// <returns></returns>
        public static bool HasIllegalContent(string s)
        {
            if (s == null || s.Trim().Length == 0)
            {
                return false;
            }
            if (s.IndexOf("<script") > -1
                || s.IndexOf("&#60script") > -1
                || s.IndexOf("&60script") > -1
                || s.IndexOf("%60script") > -1)
            {
                throw new Exception("Illegal Characters Found");
            }
            return false;
        }

        /// <summary>
        /// Wraps an anchor tag around urls.
        /// </summary>
        /// <param name="text">Text.</param>
        /// <returns></returns>
		public static string EnableUrls(string text)
		{
			string pattern = @"(http|ftp|https):\/\/[\w]+(.[\w]+)([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])";
			MatchCollection matches = Regex.Matches(text, pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
			foreach (Match m in matches) 
			{
				text = text.Replace(m.ToString(), "<a rel=\"nofollow external\" href=\"" + m.ToString() + "\">" + m.ToString() + "</a>");
			}
			return text;			
		}


        /// <summary>
        /// The only HTML we will allow is hyperlinks. 
        /// We will however, check for line breaks and replace 
        /// them with <br />
        /// </summary>
        /// <param name="stringToTransform"></param>
        /// <returns></returns>
        public static string SafeFormat(string stringToTransform)
        {
            if (stringToTransform == null)
                throw new ArgumentNullException("stringToTransform", "Cannot transform a null string.");

            stringToTransform = HttpContext.Current.Server.HtmlEncode(stringToTransform);
            string brTag = "<br />";
            return stringToTransform.Replace(Environment.NewLine, brTag);
        }

        /// <summary>
        /// Strips the RTB, whatever that is.
        /// </summary>
        /// <param name="text">Text.</param>
        /// <param name="host">Host.</param>
        /// <returns></returns>
        public static string StripRTB(string text, string host)
        {
            string s = Regex.Replace(text, "/localhost/S*Admin/", "", RegexOptions.IgnoreCase);
            return Regex.Replace(s, "<A href=\"/", "<A href=\"" + "http://" + host + "/", RegexOptions.IgnoreCase);
        }

        /// <summary>
        /// Checks the text and prepends "http://" if it doesn't have it already.
        /// </summary>
        /// <param name="text">Text.</param>
        /// <returns></returns>
        public static Uri CheckForUrl(string text)
        {
            if (text == null)
                return null;

            text = text.Trim();

            if (String.IsNullOrEmpty(text))
                return null;

            if (!text.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
            {
                text = "http://" + text;
            }

            return new Uri(text);
        }

        /// <summary>
        /// Filters text to only allow defined HTML.
        /// </summary>
        /// <param name="text">Text.</param>
        /// <returns></returns>
//        public static string ConvertToAllowedHtml(string text)
//        {
//            if (text == null)
//                throw new ArgumentNullException("text", "Cannot convert null to allowed html.");

//            NameValueCollection allowedHtmlTags = Config.Settings.AllowedHtmlTags;

//#if DEBUG
//            //Assert that the NameValueCollection is case insensitive!
//            if(allowedHtmlTags != null && allowedHtmlTags.Get("strong") != null && allowedHtmlTags.Get("STRONG") == null)
//            {
//                throw new InvalidOperationException("Darn it, it's case sensitive!" + allowedHtmlTags.Get("STRONG"));
//            }
//#endif
//            return ConvertToAllowedHtml(allowedHtmlTags, text);
//        }


        public static string ConvertToAllowedHtml(string text)
        {
            HtmlTagRegex regex = new HtmlTagRegex();
            MatchCollection matches = regex.Matches(text);

            if (matches.Count == 0)
            {
                return HtmlSafe(text);
            }

            StringBuilder sb = new StringBuilder();

            int currentIndex = 0;
            foreach (Match match in matches)
            {
                //Append text before the match.
                if (currentIndex < match.Index)
                {
                    sb.Append(HtmlSafe(text.Substring(currentIndex, match.Index - currentIndex)));
                }

                string tagName = match.Groups["tagname"].Value.ToLower(CultureInfo.InvariantCulture);

                //check each match against the list of allowable tags.

                    bool isEndTag = match.Groups["endTag"].Value.Length > 0;
                    if (isEndTag)
                    {
                        sb.Append("</" + tagName + ">");
                    }
                    else
                    {
                        sb.Append("<" + tagName + ">");
                    }
                
                currentIndex = match.Index + match.Length;
            }
            //add the remaining text.
            if (currentIndex < text.Length)
            {
                sb.Append(HtmlSafe(text.Substring(currentIndex)));
            }

            return sb.ToString();
        }

        /// <summary>
        /// Filters text to only allow defined HTML.
        /// </summary>
        /// <param name="text">Text.</param>
        /// <param name="allowedHtmlTags">The allowed html tags.</param>
        /// <returns></returns>
        public static string ConvertToAllowedHtml(NameValueCollection allowedHtmlTags, string text)
        {
            if (allowedHtmlTags == null || allowedHtmlTags.Count == 0)
            {
                //This indicates that the AllowableCommentHtml configuration is either missing or
                //has no values, therefore just strip the text as normal.
                return HtmlSafe(text);
            }
            else
            {
                HtmlTagRegex regex = new HtmlTagRegex();
                MatchCollection matches = regex.Matches(text);

                if (matches.Count == 0)
                {
                    return HtmlSafe(text);
                }

                StringBuilder sb = new StringBuilder();

                int currentIndex = 0;
                foreach (Match match in matches)
                {
                    //Append text before the match.
                    if (currentIndex < match.Index)
                    {
                        sb.Append(HtmlSafe(text.Substring(currentIndex, match.Index - currentIndex)));
                    }

                    string tagName = match.Groups["tagname"].Value.ToLower(CultureInfo.InvariantCulture);

                    //check each match against the list of allowable tags.
                    if (allowedHtmlTags.Get(tagName) == null)
                    {
                        sb.Append(HtmlSafe(match.Value));
                    }
                    else
                    {
                        bool isEndTag = match.Groups["endTag"].Value.Length > 0;
                        if (isEndTag)
                        {
                            sb.Append("</" + tagName + ">");
                        }
                        else
                        {
                            sb.Append("<" + tagName);
                            sb.Append(FilterAttributes(tagName, match, allowedHtmlTags) + ">");
                        }
                    }
                    currentIndex = match.Index + match.Length;
                }
                //add the remaining text.
                if (currentIndex < text.Length)
                {
                    sb.Append(HtmlSafe(text.Substring(currentIndex)));
                }

                return sb.ToString();
            }
        }

        public static string HtmlSafe(string text)
        {
            //replace &, <, >, and line breaks with <br />
            text = text.Replace("&", "&amp;");
            text = text.Replace("<", "&lt;");
            text = text.Replace(">", "&gt;");
            text = text.Replace("\r", string.Empty);
            text = text.Replace("\n", "<br />");
            return text;
        }

        /// <summary>
        /// Removes any non-permitted attributes for the given tagName. The permitted attributes 
        /// are determined by the given allowedHtml collection.
        /// </summary>
        /// <param name="tagName"></param>
        /// <param name="match"></param>
        /// <param name="allowedHtml"></param>
        /// <returns></returns>
        /// <remarks>This will be a high volume method, so make it as efficient as possible</remarks>
        private static string FilterAttributes(string tagName, Match match, NameValueCollection allowedHtml)
        {
            string allowedAttributesText = allowedHtml[tagName];

            if (allowedAttributesText != null && allowedAttributesText.Length > 0)
            {
                StringBuilder attrSB = new StringBuilder();

                //look to see which tag's attributes we are matching
                char[] splitter = { ',' };

                NameValueCollection attributes = GetAttributeNameValues(match);

                string[] allowedAttrs = allowedHtml[tagName].ToLower(CultureInfo.InvariantCulture).Split(splitter);

                // go thru each matched attribute, and determine if it's allowed
                foreach (string attributeName in attributes.Keys)
                {
                    foreach (string allowedAttr in allowedAttrs)
                    {
                        if (String.Equals(allowedAttr.Trim(), attributeName, StringComparison.InvariantCultureIgnoreCase))
                        {
                            // found an allowed attribute, so get the attribute value
                            string attrValue = attributes[attributeName];

                            // and now add the full attribute (key=value) to be returned
                            attrSB.Append(" " + attributeName.ToLower(CultureInfo.InvariantCulture) + "=\"" + attrValue + "\"");
                        }
                    }
                }
                return attrSB.ToString();
            }
            return string.Empty;
        }

        private static NameValueCollection GetAttributeNameValues(Match match)
        {
            CaptureCollection nameCaptures = match.Groups["attName"].Captures;
            CaptureCollection valueCaptures = match.Groups["attVal"].Captures;

            NameValueCollection attributes = new NameValueCollection();

            if (nameCaptures.Count == valueCaptures.Count)
            {
                for (int i = 0; i < nameCaptures.Count; i++)
                {
                    attributes.Add(nameCaptures[i].Value, valueCaptures[i].Value);
                }
                return attributes;
            }

            if (valueCaptures.Count == 0)
            {
                return attributes;
            }

            //this is a much more complicated issue.  
            //One of the attribute names doesn't have a value.
            //so we need to match them up somehow.
            int valueIndex = 0;
            for (int i = 0; i < nameCaptures.Count; i++)
            {
                Capture currentNameCapture = nameCaptures[i];
                Capture currentValueCapture;
                string name = currentNameCapture.Value;

                if (valueIndex == valueCaptures.Count)
                {
                    //No more values to worry about.
                    continue;
                }

                currentValueCapture = valueCaptures[valueIndex];

                //Peek ahead.
                if (i < nameCaptures.Count - 1)
                {
                    Capture peekAhead = nameCaptures[i + 1];
                    if (peekAhead.Index > currentValueCapture.Index && currentValueCapture.Index > currentNameCapture.Index)
                    {
                        attributes.Add(name, currentValueCapture.Value);
                        //We are on the right value.
                        valueIndex++;
                        continue;
                    }
                }
                else
                {
                    //we're on the last item.
                    if (currentValueCapture.Index > currentNameCapture.Index)
                    {
                        attributes.Add(name, currentValueCapture.Value);
                        //We are on the right value.
                        valueIndex++;
                        continue;
                    }
                }
            }
            return attributes;
        }


        /// <summary>
        /// Returns a string collection of URLs within the specified text.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <returns></returns>
        public static StringCollection GetLinks(string text)
        {
            StringCollection links = new StringCollection();
            string sPattern = @"(?:[hH][rR][eE][fF]\s*=)" +
                @"(?:[\s""']*)(?!#|[Mm]ailto|[lL]ocation.|[jJ]avascript|.*css|.*this\.)" +
                @"(.*?)(?:[\s>""'])";

            Regex r = new Regex(sPattern, RegexOptions.IgnoreCase);
            Match m;
            string link;
            for (m = r.Match(text); m.Success; m = m.NextMatch())
            {
                if (m.Groups.ToString().Length > 0)
                {
                    link = m.Groups[1].ToString();
                    if (!links.Contains(link))
                    {
                        links.Add(link);
                    }
                }
            }
            return links;
        }

        /// <summary>
        /// Parses the URI.
        /// </summary>
        /// <param name="uri">The URI.</param>
        /// <returns></returns>
        public static Uri ParseUri(string uri)
        {
            if (uri == null)
                return null;

            try
            {
                return new Uri(uri);
            }
            catch (UriFormatException)
            {
                return null;
            }
        }

        /**/
        /// <summary>
        /// 将HTML转为普通文本格式
        /// </summary>
        /// <param name="htmlStr">所要转换的HTML字符串</param>
        /// <param name="identiftyURL">是否自动识别URL，自动识别URL会自动去处HTML代码中的超级链接标签</param>
        /// <returns>返回普通文本</returns>
        public static string UnFormatHTML(string htmlStr, bool identiftyURL)
        {
            StringBuilder normalStr = new StringBuilder(htmlStr);

            normalStr.Replace("<br />", "\r\n");

            if (identiftyURL)
            {
                Regex linkRegex1 = new Regex("(<a href=\")[\\S]{5,}(\">)|</a>");

                normalStr = new StringBuilder(linkRegex1.Replace(normalStr.ToString(), ""));
            }

            normalStr.Replace("&quot;", "\"");
            normalStr.Replace("&lt;", "<");
            normalStr.Replace("&gt;", ">");
            normalStr.Replace("&nbsp;", " ");
            normalStr.Replace("&amp;", "&");

            return normalStr.ToString();
        }

        /// <summary>
        /// 取得字符串中的所有超链接
        /// </summary>
        /// <param name="Source">源字符串</param>
        /// <returns>超连接列表</returns>
        public static List<string> GetHyperlinkList(string Source)
        {
            List<string> MyList = new List<string>();

            Regex re = new Regex(@"http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?");
            foreach (Match m in re.Matches(Source))
            {
                MyList.Add(m.Value);
            }
            return MyList;
        }

        /// <summary>
        /// 检查字符串中的超连接数量
        /// </summary>
        /// <param name="Source">源字符串</param>
        /// <returns>超链接数量</returns>
        public static int CheckHyperlinkCount(string Source)
        {
            Regex re = new Regex(@"http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?");

            return re.Matches(Source).Count;
        }
    }
}
